#include <cstdio>
#include <systemc>

using namespace sc_core;
using namespace sc_dt;
using namespace std;

SC_MODULE(TestBlockingReadWrite) {
    sc_fifo<int> fifo;
    SC_CTOR(TestBlockingReadWrite): fifo(4) {
        SC_THREAD(producer);
        SC_THREAD(consumer);
    }
    void producer() {
        for (int i = 0; i < 8; i++) {
            fifo.write(i);
            printf("@%s %s: Produced `%d`\n", sc_time_stamp().to_string().c_str(), this->name(), i);
            wait(1, SC_NS);
        }
    }
    void consumer() {
        for (int i = 0; i < 8; i++) {
            int value = fifo.read();
            printf("@%s %s: Consumed `%d`\n", sc_time_stamp().to_string().c_str(), this->name(), value);
            wait(5, SC_NS);
        }
    }
};

SC_MODULE(TestNonBlockingReadWrite) {
    sc_fifo<int> fifo;
    SC_CTOR(TestNonBlockingReadWrite): fifo(4) {
        SC_THREAD(producer);
        SC_THREAD(consumer);
    }
    void producer() {
        for (int i = 0; i < 8; i++) {
            while (!fifo.nb_write(i)) {
                printf("@%s %s: FIFO full, producer waiting...\n", sc_time_stamp().to_string().c_str(), this->name());
                wait(fifo.data_read_event());
            }
            printf("@%s %s: Produced `%d`\n", sc_time_stamp().to_string().c_str(), this->name(), i);
            wait(1, SC_NS);
        }
    }
    void consumer() {
        for (int i = 0; i < 8; i++) {
            int value;
            while (!fifo.nb_read(value)) {
                printf("@%s %s: FIFO empty, consumer waiting...\n", sc_time_stamp().to_string().c_str(), this->name());
                wait(fifo.data_written_event());
            }
            printf("@%s %s: Consumed `%d`\n", sc_time_stamp().to_string().c_str(), this->name(), value);
            wait(4, SC_NS);
        }
        printf("@%s %s: Consumer done. \n", sc_time_stamp().to_string().c_str(), this->name());
        printf("@%s %s: Consumer fifo.num_available=%d\n", sc_time_stamp().to_string().c_str(), this->name(), fifo.num_available());
        printf("@%s %s: Consumer fifo.num_free=%d\n", sc_time_stamp().to_string().c_str(), this->name(), fifo.num_free());
    }
};

void test_sc_fifo() {
    TestBlockingReadWrite test("test_blocking");    
    TestNonBlockingReadWrite test_nb("test_non_blocking");
    sc_start(96, SC_NS);
}

template <size_t N>
SC_MODULE(TestSpawn) {
    sc_in<bool> clock;

    void process(int id) {
        while (true) {
            wait(clock.posedge_event());
            printf("@%s\tThread[%d] triggered.\n", sc_time_stamp().to_string().c_str(), id);
        }
    }

    SC_CTOR(TestSpawn) {
        for (int i = 0; i < N; ++i) {
            sc_spawn([this, i]() {
                this->process(i);
            });
        }
    }
};

void test_sc_spawn() {
    sc_clock clk("clock", 2, SC_NS);
    TestSpawn<5> test_instance("test");
    test_instance.clock(clk);

    sc_start(10, SC_NS);
}

int sc_main(int argc, char **argv) {
    if (argc < 2) {
        printf("Usage: %s <testcase>\n", argv[0]);
        printf("Available Testcases: sc_fifo, sc_spawn\n");
        return 1;
    }
    
    string testcase = argv[1];
    if (testcase == "sc_fifo") {
        test_sc_fifo();
    } else if (testcase == "sc_spawn") {
        test_sc_spawn();
    } else {
        printf("Unknown test name: %s\n", testcase.c_str());
        return 1;
    }

    return 0;
}
